#include <libseminix/client/tcb.h>
#include <libseminix/syscall.h>

struct start_tcb_args {
    void *(*start_func)(void *);
    void *start_arg;
};

static __noreturn void tcb_exit(void *result)
{
    printf("tcb exit!\n");
    for (;;);
}

static int start(void *p)
{
    struct start_tcb_args *args = p;

    tcb_exit(args->start_func(args->start_arg));
    return 0;
}

int seminix_tcb_wake_up_new(int new_tcb, int copy_tcb, unsigned long pc, unsigned long reg0, unsigned long stack, unsigned long flags,
    unsigned long *parent_tidptr, unsigned long *child_tidptr)
{
    unsigned long *tidptr, real_stack;

    /*
     * +-----+ stack top (No TCB_WAKEUP_NOPC) -> real_stack (new thread stack start)
     * |     |
     * +-----+ new_stack (TCB_WAKEUP_NOPC) -> real_stack
     * |     |
     * +-----+ parent_tidptr (TCB_WAKEUP_PARENT_GETTID)
     * |     |
     * +-----+ child_tidptr (TCB_WAKEUP_CHILD_SETTID/TCB_WAKEUP_SETTLS)
     */
    if (flags & TCB_WAKEUP_NOPC) {
        struct start_tcb_args *new_stack;

        new_stack = (struct start_tcb_args *)arch_align_stack(stack - sizeof(*new_stack));
        new_stack->start_arg = (void *)reg0;
        new_stack->start_func = (void *)pc;
        pc = (unsigned long)start;
        reg0 = (unsigned long)new_stack;
        stack = (unsigned long)new_stack;
    }

    real_stack = arch_align_stack(stack);
    if (flags & TCB_WAKEUP_PARENT_GETTID) {
        tidptr = (unsigned long *)(stack - sizeof (unsigned long));
        *tidptr = (unsigned long)parent_tidptr;
        stack = (unsigned long)tidptr;
    }

    if (flags & (TCB_WAKEUP_CHILD_SETTID | TCB_WAKEUP_SETTLS)) {
        tidptr = (unsigned long *)(stack - sizeof (unsigned long));
        *tidptr = (unsigned long)child_tidptr;
    }

    return __syscall(SYS_seminix_TCBWakeUpNew, new_tcb, copy_tcb, pc, reg0, real_stack, flags);
}

static inline int tcb_configure(int tcb, int op, tcb_config_t *config)
{
    return __syscall(SYS_seminix_TCBConfigure, tcb, op, config);
}

int seminix_tcb_configure(int tcb, int mask, tcb_config_t *tcbconfig)
{
    int op = 0;
    tcb_config_t config = { 0 };

    if (!tcbconfig)
        return -SERRNO_EINVAL;

    if (mask & TCB_CONFIG_CNODE)
        config.cnode = tcbconfig->cnode;
    if (mask & TCB_CONFIG_VSPACE)
        config.vspace = tcbconfig->vspace;
    if (mask & TCB_CONFIG_EPFAULT)
        config.epfault = tcbconfig->epfault;
    if (mask & TCB_CONFIG_IPCBUF)
        config.ipcbuf = tcbconfig->ipcbuf;
    if (mask & TCB_CONFIG_RLIMIT)
        config.rlimit = tcbconfig->rlimit;

    op = mask & (TCB_CONFIG_CNODE | TCB_CONFIG_VSPACE |
            TCB_CONFIG_EPFAULT | TCB_CONFIG_IPCBUF | TCB_CONFIG_RLIMIT);
    return tcb_configure(tcb, op, &config);
}

int seminix_tcb_setpriority(int tcb, int auth_tcb, int priority)
{
    int op = SCHED_PARAMS_SETPRIORITY;
    tcb_sched_params_t sched_params = {
        .priority = priority,
    };

    return __syscall(SYS_seminix_TCBPriority, tcb, auth_tcb, op, &sched_params);
}

int seminix_tcb_setmcpriority(int tcb, int auth_tcb, int mcpriority)
{
    int op = SCHED_PARAMS_SETMCPRIORITY;
    tcb_sched_params_t sched_params = {
        .mcpriority = mcpriority,
    };

    return __syscall(SYS_seminix_TCBPriority, tcb, auth_tcb, op, &sched_params);
}

int seminix_tcb_set_sched_params(int tcb, int auth_tcb, tcb_sched_params_t *params)
{
    int op = SCHED_PARAMS_SETALL;
    tcb_sched_params_t sched_params;

    if (!params)
        return -SERRNO_EINVAL;

    sched_params.priority = params->priority;
    sched_params.mcpriority = params->mcpriority;
    sched_params.policy = params->policy;

    return __syscall(SYS_seminix_TCBPriority, tcb, auth_tcb, op, &sched_params);
}

int seminix_tcb_get_sched_params(int tcb, tcb_sched_params_t *params)
{
    int ret;
    int op = SCHED_PARAMS_GET;
    tcb_sched_params_t sched_params;

    if (!params)
        return -SERRNO_EINVAL;

    ret = __syscall(SYS_seminix_TCBPriority, tcb, 0, op, &sched_params);
    if (ret)
        return ret;

    params->priority = sched_params.priority;
    params->mcpriority = sched_params.mcpriority;
    params->policy = sched_params.policy;

    return 0;
}

int seminix_tcb_suspend(int tcb)
{
    int op = TCB_CONTROL_SUSPEND;

    return __syscall(SYS_seminix_TCBControl, tcb, op, 0);
}

int seminix_tcb_resume(int tcb)
{
    int op = TCB_CONTROL_RESUME;

    return __syscall(SYS_seminix_TCBControl, tcb, op, 0);
}

int seminix_tcb_status(int tcb)
{
    int op = TCB_CONTROL_GETSTATUS;

    return __syscall(SYS_seminix_TCBControl, tcb, op, 0);
}

int seminix_tcb_setaffinity(int tcb, int cpu)
{
    int op = TCB_CONTROL_SETAFFINITY;

    return __syscall(SYS_seminix_TCBControl, tcb, op, (unsigned long)cpu);
}

int seminix_tcb_getaffinity(int tcb)
{
    int op = TCB_CONTROL_GETAFFINITY;

    return __syscall(SYS_seminix_TCBControl, tcb, op, 0);
}

int seminix_tcb_kill(int tcb)
{
    int op = TCB_CONTROL_KILL;

    return __syscall(SYS_seminix_TCBControl, tcb, op, 0);
}

int seminix_tcb_tid(int tcb)
{
    int op = TCB_CONTROL_GETTID;

    return __syscall(SYS_seminix_TCBControl, tcb, op, 0);
}

void seminix_yield(void)
{
    __syscall(SYS_seminix_Yield);
}
